package middleware

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"gitee.com/war2001/NiuMaEasyGo/global"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

// JWTClaims JWT声明
type JWTClaims struct {
	UserID   uint   `json:"user_id"`
	Username string `json:"username"`
	RoleID   uint   `json:"role_id"`
	jwt.StandardClaims
}

// JWTAuth JWT认证中间件
func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取JWT配置
		jwtConfig := global.Config.JWT

		// 从请求中获取令牌
		token, err := getToken(c, jwtConfig.TokenLookup)
		if err != nil {
			ResponseUnauthorized(c, "未提供有效的认证令牌")
			c.Abort()
			return
		}

		// 解析令牌
		claims, err := parseToken(token, jwtConfig.Secret)
		if err != nil {
			ResponseUnauthorized(c, fmt.Sprintf("无效的令牌: %v", err))
			c.Abort()
			return
		}

		// 检查令牌是否在黑名单中
		if isTokenInBlacklist(claims.Id) {
			ResponseUnauthorized(c, "令牌已被吊销")
			c.Abort()
			return
		}

		// 将用户信息存储到上下文中
		c.Set("user_id", claims.UserID)
		c.Set("username", claims.Username)
		c.Set("role_id", claims.RoleID)
		c.Set("jwt_claims", claims)
		c.Set("jwt_token", token)

		c.Next()
	}
}

// getToken 从请求中获取令牌
func getToken(c *gin.Context, tokenLookup string) (string, error) {
	parts := strings.Split(tokenLookup, ":")
	if len(parts) != 2 {
		return "", errors.New("无效的token_lookup配置")
	}

	method := parts[0]
	key := parts[1]

	switch method {
	case "header":
		token := c.GetHeader(key)
		if token == "" {
			return "", errors.New("未提供认证令牌")
		}
		// 处理Bearer令牌
		if global.Config.JWT.TokenType != "" && strings.HasPrefix(token, global.Config.JWT.TokenType) {
			token = strings.TrimSpace(strings.Replace(token, global.Config.JWT.TokenType, "", 1))
		}
		return token, nil
	case "query":
		token := c.Query(key)
		if token == "" {
			return "", errors.New("未提供认证令牌")
		}
		return token, nil
	case "cookie":
		token, err := c.Cookie(key)
		if err != nil || token == "" {
			return "", errors.New("未提供认证令牌")
		}
		return token, nil
	default:
		return "", errors.New("不支持的token获取方法")
	}
}

// parseToken 解析JWT令牌
func parseToken(tokenString, secret string) (*JWTClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
		// 验证签名算法
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("意外的签名方法: %v", token.Header["alg"])
		}
		return []byte(secret), nil
	})

	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, errors.New("无效的令牌")
}

// isTokenInBlacklist 检查令牌是否在黑名单中
func isTokenInBlacklist(jti string) bool {
	// 如果Redis不可用，则跳过黑名单检查
	if global.Redis == nil {
		zap.L().Warn("Redis不可用，跳过令牌黑名单检查")
		return false
	}

	// 构建黑名单键
	blacklistKey := global.Config.JWT.BlacklistPrefix + jti

	// 检查令牌是否在黑名单中
	ctx := context.Background()
	exists, err := global.Redis.Exists(ctx, blacklistKey).Result()
	if err != nil {
		zap.L().Error("检查令牌黑名单失败", zap.Error(err))
		return false
	}

	return exists > 0
}

// GenerateToken 生成JWT令牌
func GenerateToken(userID uint, username string, roleID uint) (string, error) {
	// 获取JWT配置
	jwtConfig := global.Config.JWT

	// 生成唯一ID
	jti := fmt.Sprintf("%d_%d", userID, time.Now().UnixNano())

	// 创建JWT声明
	claims := JWTClaims{
		UserID:   userID,
		Username: username,
		RoleID:   roleID,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Duration(jwtConfig.Expire) * time.Second).Unix(),
			IssuedAt:  time.Now().Unix(),
			NotBefore: time.Now().Unix(),
			Issuer:    jwtConfig.Issuer,
			Id:        jti,
		},
	}

	// 创建令牌
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// 签名令牌
	tokenString, err := token.SignedString([]byte(jwtConfig.Secret))
	if err != nil {
		return "", err
	}

	return tokenString, nil
}

// BlacklistToken 将令牌加入黑名单
func BlacklistToken(c *gin.Context) error {
	// 如果Redis不可用，则跳过黑名单操作
	if global.Redis == nil {
		return errors.New("Redis不可用，无法将令牌加入黑名单")
	}

	// 从上下文中获取令牌和声明
	claimsInterface, exists := c.Get("jwt_claims")
	if !exists {
		return errors.New("上下文中未找到JWT声明")
	}

	claims, ok := claimsInterface.(*JWTClaims)
	if !ok {
		return errors.New("JWT声明类型错误")
	}

	// 计算令牌过期时间
	expiresAt := time.Unix(claims.ExpiresAt, 0)
	ttl := time.Until(expiresAt)

	// 如果令牌已过期，则添加宽限期
	if ttl <= 0 {
		ttl = time.Duration(global.Config.JWT.BlacklistGracePeriod) * time.Second
	}

	// 将令牌加入黑名单
	ctx := context.Background()
	blacklistKey := global.Config.JWT.BlacklistPrefix + claims.Id
	err := global.Redis.Set(ctx, blacklistKey, "1", ttl).Err()
	if err != nil {
		return err
	}

	return nil
}

// RefreshToken 刷新JWT令牌
func RefreshToken(c *gin.Context) (string, error) {
	// 从上下文中获取令牌和声明
	claimsInterface, exists := c.Get("jwt_claims")
	if !exists {
		return "", errors.New("上下文中未找到JWT声明")
	}

	claims, ok := claimsInterface.(*JWTClaims)
	if !ok {
		return "", errors.New("JWT声明类型错误")
	}

	// 将当前令牌加入黑名单
	if err := BlacklistToken(c); err != nil {
		return "", err
	}

	// 生成新令牌
	return GenerateToken(claims.UserID, claims.Username, claims.RoleID)
}
